{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# **深度学习公开课 - 深度学习中的时间序列算法群**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 目录\n",
    "\n",
    "**一、Informer vs Transformer**\n",
    "\n",
    "&emsp;&emsp;1. Informer尝试解决的问题\n",
    "\n",
    "&emsp;&emsp;2. Transformer与Informer的结构差异\n",
    "\n",
    "**二、Informer原理**\n",
    "\n",
    "&emsp;&emsp;1. Attention计算改进<br>\n",
    "\n",
    "&emsp;&emsp;2. Encoder结构改进\n",
    "\n",
    "&emsp;&emsp;3. Decoder结构改进\n",
    "\n",
    "&emsp;&emsp;4. 位置编码改进\n",
    "\n",
    "**三、源码讲解**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 一、Informer vs Transformer\n",
    "\n",
    "### 1. Informer尝试解决的问题\n",
    "时间序列预测(Time-Series Forecasting)是一种较为常见的工业应用，譬如我们此前实验的股价预测就是代表性的应用方向。在实际应用中，我们希望不止预测未来单个时间步的结果，如果可以预测到未来一段时间内的结果，我们就可以提前进行资源的部署。\n",
    "\n",
    "在简单的温度预测任务中，我们使用前15天的数据预测后一天可能的温度（也就是常见的单步预测）并不难。如果要预测未来10天的温度，任务的难度就截然不同了。随着预测序列长度的增加，预测难度也越来越高。这种针对长序列的预测就是**LSTF（Long sequence time-series forecasting）**。\n",
    "\n",
    "LSTF由于与预测序列较长，模型就需要就需要拥有解决长距离依赖(long-range dependency)问题的能力，作者想到了最近几年在NLP领域大杀特杀的Transformer模型。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ">  我们在Transformer计算Attention的时候，一定会有的就是QKV矩阵的乘法。我们来回顾一下这个计算方式：\n",
    "\n",
    ">  设定矩阵的维度：\n",
    ">  假设我们有一个序列长度为 $L$，每个元素的特征维度为$D$。因此，$Q、K $和$ V $矩阵的维度都是$L×D$（或者在某些情况下，$K$ 和$ V$ 可能有不同的维度，但为了简化讨论，我们假设它们也是$L×D$。\n",
    "\n",
    "> - 计算Attention Score（时间复杂度）: \n",
    "\n",
    "> 首先，计算Query矩阵$Q$与Key矩阵$K$的转置的乘积。这是一个$L×D$乘以$D×L$的操作。\n",
    "结果是一个$L×L$的矩阵，表示序列中每个元素与其他所有元素的关联度（Attention Score）。\n",
    "因为我们需要填充这个$L×L$的矩阵，所以这个操作的时间复杂度是$O(L^2 )$。\n",
    "> - 计算更新后的值（空间复杂度）: \n",
    "\n",
    "> 接下来，将得到的Attention Score矩阵与Value矩阵$V$相乘$L×L$乘以$L×D$。\n",
    "这个操作结果是一个$L×D$的矩阵，但重要的是在这个过程中，我们需要存储大小为$L×L$的Attention Score矩阵。因此，空间复杂度同样是$O(L^2 )$。\n",
    "\n",
    "> 这只是单层的空间复杂度，如果堆叠J层encoder/decoder层，我们需要的内存空间将更大为$O(J*L^2)$。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "其实在Informer之前已经有人尝试将Transformer应用到时间序列任务中，但作者认为Transformer直接在时间序列中应用还有问题有待解决：\n",
    "\n",
    "* self-attention的时间和空间复杂度都是 $O(L^2)$，这里$L$表示序列长度\n",
    "* 长序列输入下堆叠层遇到内存瓶颈，堆叠J层encoder/decoder层让内存使用率为$O(J*L^2)$，限制模型去接受更长的输入；\n",
    "* encoder-decoder结构在解码时step-by-step，预测序列越长，预测时间也就越长"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Informer: Beyond Efficient Transformer for Long Sequence Time-Series Forecasting是来自北航的一项工作，获得了AAAI 2021的Best Paper。作者就Transformer在长序列时间序列预测问题做出了改进来解决上面的问题，由此提出了我们今天讲解的Informer。\n",
    "\n",
    "论文：[《Informer: Beyond Efficient Transformer for Long Sequence Time-Series Forecasting》](https://arxiv.org/pdf/2012.07436.pdf)\n",
    "\n",
    "Github原代码库：[Informer2020](https://github.com/zhouhaoyi/Informer2020)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Transformer与Informer"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "既然Informer是由Transformer优化，我们来对照看一下它们的整体架构图：\n",
    "\n",
    "![Transformer.png](https://fufanshare.oss-cn-beijing.aliyuncs.com/DeepLearning/Informer/Transformer.png)![informer架构.png](https://fufanshare.oss-cn-beijing.aliyuncs.com/DeepLearning/Informer/informer%E6%9E%B6%E6%9E%84.png)\n",
    "\n",
    "* 首先，直观来看Transformer的整体架构是“方方正正”的，而Informer中出现了“梯形”的模块。为什么它用“梯形”来表示呢？同时发现Transformer架构中Encoder和Decoder旁边都有标注“ $N$ X”，表示堆叠了N层。Informer中的Encoder并没有标注堆叠，取而代之的是一大一小两个梯形。\n",
    "\n",
    "* 其次，我们看梯形模块上的标注文字，发现Attention注意力机制部分，Transformer是【Multi-head Attention】而Informer对注意力机制增加了一部分描述【ProbSparse】。我们稍后看一下这个注意力机制和传统的有什么不同？\n",
    "\n",
    "* 最后，我们发现Informer架构图中很大一部分在画输入\\输出的图。为什么它不像Transformer一样直接写明“InputEmbedding”“OutputEmbedding”？而是画了更为直观的彩色填充的embedding表示图。\n",
    "\n",
    "带着对架构图直观感受的问题，我们来逐步分解看一下Informer究竟改进了上面内容，为什么要这样绘制架构图。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 二、Informer原理"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1、Attention计算\n",
    "在长序列中，并不是每一个位置的attention都很重要。\n",
    "\n",
    "Transformer里，我们计算QKV的时候，看一下QK点积的热力图，越亮的部分表示QK相关性越高。热力图中大部分为黑色，实验发现对于每个Q只有一小部分的K和它有较强的关系。\n",
    "\n",
    "就下图来看，8000个样本，相关性较高的可能只有2000个不到。大部分时候QK的关系都接近于0。\n",
    "\n",
    "![Attention里QK点积-长尾效应.png](https://fufanshare.oss-cn-beijing.aliyuncs.com/DeepLearning/Informer/Attention%E9%87%8CQK%E7%82%B9%E7%A7%AF-%E9%95%BF%E5%B0%BE%E6%95%88%E5%BA%94.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如下图，纵坐标为Q，横坐标为K。每一行即为一个Q与所有K相关性的结果。 \\\n",
    "红色部分就是一个“积极”的Q，我们可以从图中明显看出它和哪个K相关性较高。 \\\n",
    "绿色部分就是一个“懒惰”的Q，它和所有的K关系都很“一般”。\n",
    "\n",
    "![probsparse_intro.png](https://fufanshare.oss-cn-beijing.aliyuncs.com/DeepLearning/Informer/probsparse_intro.png)\n",
    "\n",
    "在实际计算中，这些“懒惰”的Q不仅无法提供有效的价值，而且在Q里大部分都是这些“懒惰”的家伙。 \\\n",
    "只选取“积极”的Q来计算注意力机制，而舍弃掉“懒惰”的Q的想法随之诞生。这就是Informer论文的核心：**ProbSparse Attention**。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**如何去筛选“积极”的Q呢？** \\\n",
    "ProbSparse Self-attention 采取了和均匀分布相比较的思路。 \\\n",
    "均匀分布就像上图中的虚线部分，是一条直线。对于每个Q计算它和均匀分布的差异，差异越大就越“活跃”。\n",
    "\n",
    "为了进一步讨论自注意力机制，让 $q_{i} ,k_{i}  ,v_{i} $ 分别代表 Q、K、V 中的第i行。\n",
    "\n",
    "注意力的概率分布：$p(k_{j}|q_{i})=\\frac{k(q_{i},k_{j})}{\\sum_{l}k(q_{i},k_{l})}$ \\\n",
    "均匀分布：$q(k_{j}|q_{i})=\\frac{1}{L_{k}}$ \n",
    "\n",
    "* 这里$k(q_{i},k_{l})$选用的计算方式是$e^{\\frac{q_{i}k_{l}^T}{\\sqrt{d}}}$, \n",
    "> - 在注意力机制中使用Softmax函数来得到概率分布，我们用e指数函数来计算，可以确保所有权重都为正数，且和为1，可以解释为概率。 \n",
    "> - 指数函数$e^x$在x>0的时候增长较快，我们常说指数级增长，有助于模型在确定注意力时放大那些更相关（点积后结果更大）的键值对的影响。\n",
    "> - 指数函数的导数仍然是指数函数，这意味着在反向传播过程中，梯度不会立即消失或爆炸，有助于稳定学习过程。\n",
    "\n",
    "* $L_k$ 就是query查询向量的长度\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "想要去度量两种分布的距离，自然想到使用KL散度公式。 \\\n",
    "KL散度(Kullback-Leibler divergence)又称相对熵(relative entropy)是描述两个概率分布P和Q差异的一种方法，是非对称的。 \\\n",
    "离散的KL散度定义：$D(P||Q)=\\sum_{i\\in{x}}P(i)*[log(\\frac{P(i)}{Q(i)})]$ \\\n",
    "连续的KL散度定义：$D(P||Q)=\\int_{x}P(x)*[log(\\frac{P(i)}{Q(i)})]dx$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们将上面的两种分布带入KL散度的计算公式：\n",
    "\n",
    "![KL散度公式.png](https://fufanshare.oss-cn-beijing.aliyuncs.com/DeepLearning/Informer/KL%E6%95%A3%E5%BA%A6%E5%85%AC%E5%BC%8F.png)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "舍弃常数项，最终定义第i个query的“稀疏性度量”如下： \n",
    "$$M(q_{i},K)={ln\\sum_{j=1}^{L_{k}}exp\\frac{q_{i}k_{j}^T}{\\sqrt{d}}}-\\frac{1}{L_{k}}*\\sum_{j=1}^{L_{k}}\\frac{q_{i}k_{j}^T}{\\sqrt{d}}$$\n",
    "公式中第一项是$q_{i}$和所有k内积的Log-Sum-Exp（LSE），第二项是算术平均数，散度越大，概率分布越多样化，越可能存在关注重点。\n",
    "但上述公式会有两个问题：\n",
    "\n",
    "● 点积对的计算复杂度是$O(L_{Q}*L_{K})$；\n",
    "\n",
    "● LSE计算存在数值不稳定的风险，因为$e^x$形式下，可能会数据溢出报错。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "为了解决这两个问题，作者采用了以下的方式：\n",
    "\n",
    "● 随机采样：随机选择其中的top u ( $U = L_Q ln L_K$ ) 个点积对计算 $\\bar M(q_i,K)$ 。\n",
    "这样使复杂度降低到$ O(LlnL)$。（在代码的默认参数中U=25）\n",
    "\n",
    "● 用$max(\\frac{q_{i}k_{j}^T}{\\sqrt{d}})$替换$ln\\sum_{j=1}^{L_{k}}exp\\frac{q_{i}k_{j}^T}{\\sqrt{d}}$ 。直接选择最大值与均匀分布算差异可以进一步加速计算过程。\n",
    "\n",
    "由此第i个query的“稀疏性度量”表达式改写为：\n",
    "\n",
    "$$\\bar M(q_{i},K)={max(\\frac{q_{i}k_{j}^T}{\\sqrt{d}}})-\\frac{1}{L_{k}}*\\sum_{j=1}^{L_{k}}\\frac{q_{i}k_{j}^T}{\\sqrt{d}}$$\n",
    "\n",
    "这样我们可以得到每个q的 $ \\bar M$得分，得分越大这个q越“积极”。\n",
    "\n",
    "然后我们在全部的q里选择 $ \\bar M$得分较大的前U个，定义为“积极”的q。来进行QKV矩阵乘法的计算。（U的取值根据实际情况来定，原论文中序列长度为96，作者定义U=25，即选择得分较大的25个Q。）\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "明白了如何选择“积极”的q之后，大家肯定会想\n",
    "\n",
    "* 为了求解这个度量得分$ \\bar M$，还是要计算全部的QK点积，这样难道不是更“复杂”了吗？并没有减少计算量或者加快计算速度。 \n",
    "* 而且只计算“积极”的Q，“懒惰”的q就完全抛弃吗？矩阵的形状不就发生了变化吗？这会影响后续的计算吗？\n",
    "\n",
    "我们来看看作者是如何解决这两个问题的，这部分隐藏在作者的实际代码实现里。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里首先看第一点度量得分$ \\bar M$的计算，我们只是想用这个得分去筛选“积极”的q，用所有的k参与计算，计算量确实太大了。实际上并没有计算全部的QK点积，而是进行了一个抽样。 \\\n",
    "正常情况下如上推导，将每个“积极”的Q和所有k（原论文中是96个k）计算，但在论文源码的实践中，在计算前会随机选取一部分k（原论文中是25个k）来计算，也可以作为它的分布。\n",
    "\n",
    "![部分K即可计算M.png](https://fufanshare.oss-cn-beijing.aliyuncs.com/DeepLearning/Informer/%E9%83%A8%E5%88%86K%E5%8D%B3%E5%8F%AF%E8%AE%A1%E7%AE%97M.png)\n",
    "\n",
    "直观的从上图可以看出。我们只选取9个k也可以大致知道这个曲线变化的情况。\n",
    "\n",
    "由此，我们只需要一部分的k就可以对全部Q的“积极”性进行排序，然后进行选择。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "然后我们来看一下对于“懒惰”的q是不是就完全不要了呢？\n",
    "\n",
    "**ProbSparse Self-attention**\n",
    "\n",
    "根据上述的内容，我们允许每个k仅关注U个“积极”的q来获得ProbSparse自注意力：\n",
    "\n",
    "$$A(Q,K,V)=Softmax(\\frac{\\bar{Q} K^T}{\\sqrt{d}})V$$\n",
    "\n",
    "这里的$ \\bar{Q} $就是top u个queries选拔后的。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "回顾一下原始的Transformer计算时的过程：\n",
    "\n",
    "![原始Transformer.png](https://fufanshare.oss-cn-beijing.aliyuncs.com/DeepLearning/Informer/%E5%8E%9F%E5%A7%8BTransformer.png)\n",
    "\n",
    "我们按照原论文中的数据，假设序列长度为96。 \\\n",
    "这里的维度是：$softmax(\\frac{QK^T}{\\sqrt{d}})_{96*96} * V_{96*64}=Z_{96*64}$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果我们选择U=25个“积极”的Q来计算：\n",
    "\n",
    "$softmax(\\frac{ \\bar Q K^T}{\\sqrt{d}})_{25*96} * V_{96*64}=Z_{25*64}$\n",
    "\n",
    "对于剩余的“懒惰”的q，作者采取的办法是，用V向量的平均来代替剩余的 Lazy queries 对应的时间点的向量。\n",
    "\n",
    "![lazy处理.png](https://fufanshare.oss-cn-beijing.aliyuncs.com/DeepLearning/Informer/lazy%E5%A4%84%E7%90%86.png)\n",
    "\n",
    "这样做的原因是，通过判定得到的 Lazy queries 的概率分布本来就接近均匀向量，也就是说这个时间点对96个时间点的注意力是均衡的，每个都差不多，所以他们atteniton计算后的向量也应当接近于全部V向量的平均。\n",
    "\n",
    "通过这样的“填充”我们最后的矩阵Z依旧是96*64的维度，但因为是在计算完“积极”Q和K点积后再“填充，计算的时间和空间复杂度都大大降低了。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们在理解公式的基础上再梳理一下源码的实现步骤：\n",
    "\n",
    "（1）输入序列长为96，在K中进行随机采样，随机选取25个K来进行后续计算。 \\\n",
    "（2）选择“积极”的Q进行计算。  （正常情况下如上推导，将每个“积极”的Q和96个k计算，但在论文源码的实践中，不需要计算那么多，用我们上一步随机选取的25个k来计算，也可以作为它的分布） \\\n",
    "（3）每个Q有25个得分,选择25个得分里最大的作为这个q的最终得分$\\bar M(q_{i},K)$，在96个Q中选择最终得分$\\bar M$最大的25个q。 \\\n",
    "（4）计算这25个q的QK内积，其余位置直接用V的均值来代替，得到最终的矩阵Z。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2、Encoder结构"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![enoder在整体架构.png](https://fufanshare.oss-cn-beijing.aliyuncs.com/DeepLearning/Informer/enoder%E5%9C%A8%E6%95%B4%E4%BD%93%E6%9E%B6%E6%9E%84.png)\n",
    "\n",
    "在这个架构中我们拿出一个Encoder（也就是一个梯形）来看作者在哪些方面做了改进。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "论文中提出了一种新的EncoderStack结构，由多个Encoder和蒸馏层组合而成。我们拿出单个Encoder来看，如下图：\n",
    "\n",
    "![EnoderBlok.png](https://fufanshare.oss-cn-beijing.aliyuncs.com/DeepLearning/Informer/EnoderBlok.png)\n",
    "\n",
    "这里先看一下左边绿色的部分，是Encoder的输入。由上面深绿色的scalar和下面浅绿色的stamp组成。\n",
    "* 深绿色的scalar就相当于我们之前Transformer里的input-embedding 是我们原始输入的向量。\n",
    "* 浅绿色的stamp包含之前Transformer里的 Positional Ecoding（位置编码）来表示输入序列的相对位置。在时序预测任务中，这个stamp其实由LocalTimeStamp（也就是位置编码）和GobalTimeStamp（与时序相关的编码）共同构成。\n",
    "\n",
    "我们在后面编码部分再详细展开Encoder的输入部分。我们来看一下后面Encoder的结构。\n",
    "\n",
    "Encoder的作用是**Self-attention Distilling**，由于ProbSparse自相关机制有很多都是用V的mean填充的，所以天然就存在冗余的attention sorce ,因此在相邻的Attention Block之间应用卷积与池化来对特征进行下采样，所以作者在设计Encoder时，采用蒸馏的操作不断抽取重点特征，从而得到值得重点关注的特征图。\n",
    "\n",
    "从图中看到每个Encoder有3个Attention Block;每个Attention Block内有n个头权重矩阵。 \\\n",
    "Encoder中重复的部分可以由下面的公式所概况： \n",
    "$$X_{j+1}^t=MaxPool(ELU(Conv1d([X_j^t]_{AB})))$$\n",
    "\n",
    "$[.]_{AB}$代表Attention Block，包括多头ProbSparse自相关,  \\\n",
    "$Conv1d(.)$是1D-CNN，使用ELU(·)激活函数在时间维度上执行一维卷积过滤器， \\\n",
    "外面套了max-pooling层（stride=2）， \n",
    "\n",
    "每次会下采样一半来节省了内存占用率（原来序列长为96，下一次输入序列就变为48，同时QK的采样也会同时变小25->20）。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Informer的架构图并没有像Transformer一样在Encoder的左边标注“ $ N $ X”来表示N个Encoder的堆叠，而是一大一小两个梯形。实际上Iformer确实就只有一大一小这两个Encoder。\n",
    "\n",
    "横向看完单个Encoder（也就是架构图中左边的大梯形，是整个输入序列的主堆栈），我们来看架构图中为什么右边还有一个小梯形？怎么和大梯形来堆叠的呢？\n",
    "\n",
    "![Encoder纵向.jpeg](https://fufanshare.oss-cn-beijing.aliyuncs.com/DeepLearning/Informer/Encoder%E7%BA%B5%E5%90%91.jpeg)\n",
    "\n",
    "作者为了提高encoder的鲁棒性，还提出了一个strick。上面的Encoder是主stack，输入整个embedding后经过了三个Attention Block，最终得到Feature Map。 \\\n",
    "还可以再复制一份具有一半输入的embedding（直接取96个时间点的后48个），让它让经过两个Attention Block(标注处理流程是“similar operation”也就是相同的处理流程)，最终会得到和上面维度相同的Feature Map，然后把两个Feature Map拼接。作者认为这种方式对短周期的数据可能更有效一些。\n",
    "\n",
    "下面我自己绘制了一个Encoder堆叠的展示图。途中的维度数据来源于论文源码（batch=32的8头注意力机制，序列长度为96）。\n",
    "\n",
    "![自绘图Encoder的堆叠.png](https://fufanshare.oss-cn-beijing.aliyuncs.com/DeepLearning/Informer/%E8%87%AA%E7%BB%98%E5%9B%BEEncoder%E7%9A%84%E5%A0%86%E5%8F%A0.png)\n",
    "\n",
    "图里的Encoder1 和 Encoder2 分布得到维度为25和26的Feature Map，将这两个输出连接起来，就能得到最终的51维输出了。（这里的51因为在卷积过程中的取整，导致这个数看起来不太整）\n",
    "\n",
    "最后我们回顾整个Encoder的输入输出维度：输入为 $32*8*96*512$，输出为 $32*7*51*512$。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "有下面的热力图发现，使用这样的注意力机制和Encoder结构，特征更为明显且与之前的模式基本一致。\n",
    "\n",
    "![蒸馏前后特征对比.png](https://fufanshare.oss-cn-beijing.aliyuncs.com/DeepLearning/Informer/%E8%92%B8%E9%A6%8F%E5%89%8D%E5%90%8E%E7%89%B9%E5%BE%81%E5%AF%B9%E6%AF%94.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3、Decoder结构"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里我们先回顾一下Transformer原论文中执行翻译任务的动态解码的过程。\n",
    "\n",
    "![step-by-step.gif](https://fufanshare.oss-cn-beijing.aliyuncs.com/DeepLearning/Informer/step-by-step.gif)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们看一下上面的图，Decoder输出后经过Linear+Softmax，Linear将输出扩展到与vocabulary size一样的维度，再经过softmax，就可以选择最高概率的词作为预测结果。\n",
    "\n",
    "我们再梳理一下训练好模型执行预测的过程：\n",
    "* Decoder：Encoder对embedding操作后的KV+开始符号=预测结果i\n",
    "* Decoder：Encoder对embedding操作后的KV+\"i\"=预测结果am\n",
    "* Decoder：Encoder对embedding操作后的KV+\"i am\"=预测结果a\n",
    "* Decoder：Encoder对embedding操作后的KV+\"i am a\"=预测结果student\n",
    "* Decoder：Encoder对embedding操作后的KV+\"i am a student\"=预测结果 结尾符号\n",
    "\n",
    "不难看出Decoder每一步的输出都需要前一步的结果作为输入才可以，这就是step-by-step动态解码的过程。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Informer是如何操作的呢？\n",
    "\n",
    "Informer的Decoder下图所示，由2层相同的多头Attention堆叠而成。\n",
    "\n",
    "![informer架构.png](https://fufanshare.oss-cn-beijing.aliyuncs.com/DeepLearning/Informer/informer%E6%9E%B6%E6%9E%84.png)\n",
    "\n",
    "Decoder的输入如下：\n",
    "$X_{de}=\\{X_{token},X_0\\}$ \\\n",
    "我们发现输入序列是由两部分拼接而来的，$X_{token} \\in R^{L_{token}*d_{model}}$是开始的token，$X_{0} \\in R^{L_{y}*d_{model}}$是用0填充预测序列。\n",
    "\n",
    "Informer《Informer: Beyond Efficient Transformer for Long Sequence Time-Series Forecasting》的论文题目不难看出它是为了长序列时间序列预测而改进的模型。我们就举一个不同天数温度预测的例子，来看一下这个decoder的输入。\n",
    "\n",
    "![deoder输入例子.png](https://fufanshare.oss-cn-beijing.aliyuncs.com/DeepLearning/Informer/deoder%E8%BE%93%E5%85%A5%E4%BE%8B%E5%AD%90.png)\n",
    "\n",
    "如果我们想要预测7天的温度，decoder就需要输入前面1-7天的温度，后面用0填充8-14天需要预测的温度的位置，这就是一种Mask的机制。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在源码中Decoder输入长度为72，其中 前48是真实值，后24是预测值。\n",
    "\n",
    "* 第一步是加上mask后做自身的ProbAttention\n",
    "* 第二步是自身计算完Attention后与encoder计算Attention"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我这里自己画了一个图来辅助直观理解decoder的处理过程，维度的具体数值来源于论文源码。\n",
    "\n",
    "![Decoder.png](https://fufanshare.oss-cn-beijing.aliyuncs.com/DeepLearning/Informer/Decoder.png)\n",
    "\n",
    "这部分上面两个Encoder堆叠的部分我们上面已经明白了。下面Decoder输入的时候，我们将所有需要预测的部分都用0来填充作为掩码。\n",
    "\n",
    "然后增加掩码Mask后的embedding经过了ProbSparse-Attention，需要注意的一个细节是这里筛选“积极”的q之后，对于“懒惰”的q的填充方式，不同于encoder部分用V的均值（mean）填充，而是用 Cumsum，也就是每一个queries之前所有时间点V向量的累加，来防止模型关注未来的信息。\n",
    "\n",
    "得到来自Encoder的KV和Decoder第一步attention的Q之后，进行的是传统Transformer里的“Multi-head Attention”计算。在结构图中也可以看到这部分是一个“矩形”。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Generative Style Decoder**\n",
    "**我们简而言之，在Decoder部分的改进就是不同于Transformer原始架构的“一个个”预测，而是“一块块”预测。**\n",
    "\n",
    "通过上图我们可以直观看出，一个forward就可以得到所有的24个预测结果。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4、位置编码"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "位置编码部分虽然不是Informer模型论文的主要创新点。从论文附录来看Informer在位置编码部分增加了比较丰富的信息，我们可以从中学习到一些启发。\n",
    "\n",
    "![位置编码.png](https://fufanshare.oss-cn-beijing.aliyuncs.com/DeepLearning/Informer/%E4%BD%8D%E7%BD%AE%E7%BC%96%E7%A0%81.png)\n",
    "\n",
    "从上图看到，在原始向量上不止增加了Transformer架构必备的PositionEmbedding（位置编码）还增加了与时间相关的各种编码。\n",
    "\n",
    "在 LSTF 问题中，捕获远程独立性的能力需要全局信息，例如分层时间戳（周、月和年）和不可知时间戳（假期、事件）。\n",
    "具体在这里增加什么样的GlobalTimeStamp还需要根据实际问题来确认，如果计算高铁动车车站的人流量，显然“假期”的时间差就是十分重要的。如果计算公交地铁等通勤交通工具的人流量，显然“星期”可以更多的揭示是否为工作日。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "到此我们就学习完了Informer模型的全部改进内容。我们通过下面的表格来对照回顾一下：\n",
    "\n",
    "|Motivation （Transformer的不足）|Contribution （Informer的改进）|\n",
    "| ----  | ----  |\n",
    "|  self-attention平方级的计算复杂度  |提出ProbSparse Self-attention筛选出重要的query，降低计算的复杂度 |\n",
    "| 堆叠多层网络，内存占用遇到瓶颈  | 提出Self-attention Distilling 减少维度和网络参数  |\n",
    "| step-by-step解码预测，速度较慢  | 提出Generative Style Decoder，一步得到所有预测结果  |\n",
    "\n",
    "总体来说，针对Transformer的三点不足，Informer分别在Attention的计算方式，Encoder的蒸馏堆叠和Decoder的生成式预测做出了改进。使得它更适用于长序列的时间序列预测问题。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 三、源码解读"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Github原代码库：[Informer2020](https://github.com/zhouhaoyi/Informer2020)\n",
    "\n",
    "项目结构如下图：\n",
    "\n",
    "![项目结构](https://fufanshare.oss-cn-beijing.aliyuncs.com/DeepLearning/Informer/%E9%A1%B9%E7%9B%AE%E7%BB%93%E6%9E%84.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里展开看一下main_informer.py里的全部参数\n",
    "\n",
    "|参数名称|参数类型|参数讲解|\n",
    "|--------|-------|-------|\n",
    "|model|str|这是一个用于实验的参数设置，其中包含了三个选项: informer, informerstack, informerlight根据实验需求，可以选择其中之一来进行实验，默认是使用informer模型。\n",
    "|data|str|数据,这个并不是数据集文件，而是你想要用官方定义的方法还是你自己的数据集进行定义数据加载器，如果是自己的数据集就输入custom（这也是这个开源项目做的比较好的地方，项目本身可以选择用自己的数据集）\n",
    "|root_path|str|这个是文件的路径，不要到具体的文件，到目录级别即可。\n",
    "|data_path|str|这个填写文件的名称。\n",
    "|features|str|这个是特征有三个选项M，MS，S。分别是多元预测多元，多元预测单元，单元预测单元。\n",
    "|target|str|这个是数据集中想要预测那一列数据，假设预测的是油温OT列就输入OT即可。\n",
    "|freq|str|时间的间隔，数据集每一条数据之间的时间间隔。\n",
    "|checkpoints|str|训练出来的模型保存路径\n",
    "|seq_len|int|用过去的多少条数据来预测未来的数据\n",
    "|label_len|int|可以裂解为更高的权重占比的部分,要小于seq_len\n",
    "|pred_len|int|预测未来多少个时间点的数据\n",
    "|enc_in|int|数据有多少列,要减去时间那一列\n",
    "|dec_in|int|数据有多少列,要减去时间那一列\n",
    "|c_out|int|如果features填写的是M那么和上面就一样，是数据列数，如果填写的MS那么这里要输入1因为你的输出只有一列数据。\n",
    "|d_model|int|用于设置模型的维度，默认值为512。可以根据需要调整该参数的数值来改变模型的维度\n",
    "|n_heads|int|用于设置模型中的注意力头数。默认值为8，表示模型会使用8个注意力头.(有时也会用数据有多少列作为头数，可以根据自己实际数据集情况设定)\n",
    "|e_layers|int|用于设置编码器的层数\n",
    "|d_layers|int|用于设置解码器的层数\n",
    "|s_layers|str|用于设置堆叠编码器的层数\n",
    "|d_ff|int|模型中全连接网络（FCN）的维度，默认值为2048\n",
    "|factor|in|ProbSparse自注意力中的因子，默认值为5\n",
    "|padding|int|填充类型，默认值为0，如果不够数据就填写0.\n",
    "|distil|bool|是否在编码器中使用蒸馏操作。使用--distil参数表示不使用蒸馏操作，默认为True也是我们的论文中比较重要的一个改进。\n",
    "|dropout|float|丢弃的概率，防止过拟合\n",
    "|attn|str|编码器中使用的注意力类型，默认为\"prob\"论文的主要改进点，提出的注意力机制。\n",
    "|embed|str|时间特征的编码方式，默认为\"timeF\"\n",
    "|activation|str|激活函数\n",
    "|output_attention|bool|是否在编码器中输出注意力，默认为False\n",
    "|do_predict|bool|是否进行预测\n",
    "|mix|bool|在生成式解码器中是否使用混合注意力，默认为True\n",
    "|cols|str|从数据文件中选择特定的列作为输入特征，不常用\n",
    "|num_workers|int|线程（windows最好设置成0否则会报线程错误,linux系统随便设置）\n",
    "|itr|int|实验运行的次数，默认为2\n",
    "|train_epochs|int|训练的次数\n",
    "|batch_size|int|一次往模型力输入多少条数据\n",
    "|patience|int|早停机制，如果损失多少个epochs没有改变就停止训练\n",
    "|learning_rate|float|学习率\n",
    "|des|str|实验描述，默认为\"test\"\n",
    "|loss|str|损失函数，默认为\"mse\"\n",
    "|lradj|str |学习率的调整方式，默认为\"type1\"\n",
    "|use_amp|bool|混合精度训练，\n",
    "|inverse|bool|是否将归一化后的数据转换为原始值，这里默认为False，如果你想要转换为原来的数据改成True。\n",
    "|use_gpu|bool|是否使用GPU训练，根据自身来选择\n",
    "|gpu|int|GPU的编号\n",
    "|use_multi_gpu|bool|是否使用多个GPU训练。\n",
    "|devices|str|GPU的编号\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
